Skip to content

refactor(club-meeting): 도메인 책임을 객체로 이동 - #293

Draft
MODUGGAGI wants to merge 11 commits into
developfrom
refactor/292/club-meeting-domain-refactor
Draft

refactor(club-meeting): 도메인 책임을 객체로 이동#293
MODUGGAGI wants to merge 11 commits into
developfrom
refactor/292/club-meeting-domain-refactor

Conversation

@MODUGGAGI

Copy link
Copy Markdown
Collaborator

🚀 변경사항

  • 팀 편성 책임을 Meeting.organizeTeamsTeam.replaceMembers로 이동해 서비스가 조회와 저장에 집중하도록 정리했습니다.
  • 동일한 팀원 구성은 자식 엔티티를 불필요하게 교체하지 않고, 유지되는 팀의 identity를 보존합니다.
  • 한줄평 생성·수정·삭제와 별점 합계 변경 책임을 Meeting aggregate로 이동했습니다.
  • 발제와 한줄평의 작성자·운영진 권한 판단을 ClubMeetingActor 기반 도메인 명령으로 이동했습니다.
  • 발제 응답의 author 판별은 기존 의도대로 전역 memberId를 사용하고, 수정·삭제 권한은 clubMemberId를 사용하도록 역할을 분리했습니다.
  • 최대 12개 팀 편성 흐름에서 팀 자식 컬렉션 조회를 묶도록 @BatchSize(size = 12)를 적용했습니다. DB 스키마와 연관관계 매핑은 변경하지 않았습니다.
  • 단위·서비스·영속성·random-port HTTP 회귀 테스트로 기존 오류 우선순위와 DB 상태 변화를 고정했습니다.

🔗 관련 이슈

✅ 체크리스트

  • 로컬에서 테스트 완료
  • 코드 리뷰 준비 완료

📝 특이사항

  • public API, DTO, Event, HTTP 경로·응답·오류 계약은 변경하지 않았습니다.
  • 다른 production 모듈, DB migration, JPA 연관관계 매핑, transaction/retry/flush/event 순서, TeamTopic 동시성 로직은 변경하지 않았습니다.
  • 작업 중 논의를 통해 동일 구성 no-op, aggregate의 organizeTeams 책임, @BatchSize(12), 비교를 위한 단계 내 중간 커밋을 명시적으로 적용했습니다.
  • 검증 결과:
    • clubMeeting + Modulith: 50/50 통과
    • 전체 테스트: 419/419 통과
    • 실제 HTTP/DB 회귀: 3/3 통과
    • Final review 5개 관점과 3가설 런타임 감사: 모두 PASS
  • 영속성 통합 검증은 H2 기반이므로 MySQL 고유 실행 계획 차이는 잔여 위험으로 남습니다.

MODUGGAGI added 10 commits July 11, 2026 19:54
팀 구성 변경의 판단과 순서를 aggregate root에 모으고 서비스에는 검증, 조회, 저장 orchestration만 남긴다.

Constraint: 유지되는 Team과 TeamTopic의 영속성 identity를 보존해야 함
Rejected: 모든 Team 삭제 후 재생성 | TeamTopic과 기존 Team identity가 소실됨
Confidence: high
Scope-risk: moderate
Directive: Meeting-Team 매핑을 바꿀 때 cascade와 orphanRemoval 대체 전략을 먼저 마련할 것
Tested: ./gradlew test --tests checkmo.clubMeeting.internal.entity.MeetingTest --tests checkmo.clubMeeting.internal.entity.MeetingTeamPersistenceTest --tests checkmo.clubMeeting.internal.service.command.ClubMeetingCommandServiceTest
Not-tested: MySQL/Testcontainers 영속성 검증
팀 편성 컬렉션과 연관관계 조작을 aggregate 내부로 숨기고, command service가 별도 Team 조회 없이 Meeting의 organizeTeams만 호출하도록 정리한다.

Constraint: 기존 HTTP 계약과 orphanRemoval 영속화 결과를 유지
Rejected: TeamRepository로 기존 팀을 별도 조회 | Meeting 컬렉션과 중복 조회 및 책임 누출
Confidence: high
Scope-risk: moderate
Directive: 팀 편성 변경은 Meeting.organizeTeams를 통해 수행
Tested: 집중 테스트, clubMeeting 모듈 테스트, Modulith 검증, 실제 팀 편성 API 흐름
Not-tested: MySQL 실행 계획
팀 최대 개수인 12에 맞춰 Team의 팀원과 발제 선택 컬렉션을 같은 영속성 컨텍스트에서 묶어 조회한다.

Constraint: 팀 번호는 1부터 12까지로 제한
Rejected: Team마다 자식 컬렉션을 개별 지연 조회 | 최대 1+N 쿼리 발생
Confidence: high
Scope-risk: narrow
Directive: 팀 최대 개수 정책 변경 시 BatchSize 값도 함께 검토
Tested: RED 2건, 집중 테스트, clubMeeting 모듈 테스트, Modulith 검증, 실제 API 흐름, Hibernate IN(12) SQL
Not-tested: MySQL 실행 계획
서비스의 작성자·운영진 판단을 clubMeeting 도메인 명령으로 이동하고 Long 식별자 전환 전의 발제 작성자 응답 의미를 복원한다.

Constraint: API·DTO·Event·JPA·트랜잭션·retry·TeamTopic 계약 유지
Rejected: Converter에 clubMemberId 전달 | 기존 memberId 기반 작성자 의미를 바꿈
Confidence: high
Scope-risk: moderate
Directive: Stage 8에서 HTTP·AOP·DB reload 회귀를 확인할 것
Tested: 타깃 33/33 재실행, clubMeeting+Modulith 50/50 재실행, review-work 5/5 통과
Not-tested: 전체 저장소 테스트 및 실제 MySQL persistence
Plan: .omo/plans/club-meeting-domain-refactor.md
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3504f67b-060a-4978-a41f-20f16178ce91

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/292/club-meeting-domain-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the club meeting domain logic by encapsulating business operations within the domain entities (Meeting, Team, Topic, BookReview) and simplifying the command services. A critical bug was identified in the book review revision logic where updating the review's rate before subtracting the old rate causes incorrect total rate recalculations. Corrective suggestions were provided along with the necessary updates to the corresponding test assertions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +118 to +126
private void reviseBookReview(BookReview review, String description, double newRate) {
double oldRate = review.getRate();
review.updateBookReview(description, newRate);

if (oldRate != newRate) {
subtractSumRate(oldRate);
addSumRate(newRate);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

🐛 버그 리포트: 별점 수정 시 합계 재계산 로직의 왜곡 발생

Meeting.reviseBookReview 메서드에서 review.updateBookReview(description, newRate)를 호출하여 BookReview 객체의 별점(rate)을 먼저 변경한 뒤, subtractSumRate(oldRate)를 호출하고 있습니다.

이때, 만약 subtractSumRate 내부에서 this.sumRate < rate 조건이 참이 되어 별점 합계 재계산(this.bookReviews.stream().mapToDouble(BookReview::getRate).sum())이 수행되면 심각한 버그가 발생합니다.

🔍 원인 분석

  1. review.updateBookReview가 이미 호출되었으므로, this.bookReviews 목록에 있는 해당 리뷰의 별점은 이미 **새로운 별점(newRate)**으로 반영되어 있습니다.
  2. 따라서 재계산된 sumRate는 이미 newRate를 포함하고 있으며, oldRate는 포함하고 있지 않습니다.
  3. 하지만 subtractSumRate는 재계산된 값에서 다시 oldRate를 차감하고, 이후 reviseBookReview에서 addSumRate(newRate)를 호출하여 newRate를 한 번 더 더하게 됩니다.
  4. 결과적으로 재계산이 발생할 경우 sumRate(다른 리뷰들의 합 + newRate) - oldRate + newRate가 되어, 실제 별점 합계보다 newRate - oldRate 만큼 왜곡된 잘못된 값이 저장됩니다.

🛠️ 해결 방안

subtractSumRate(oldRate)를 호출하여 기존 별점을 먼저 차감한 이후에 review.updateBookReview를 호출하고, 마지막으로 addSumRate(newRate)를 호출하도록 순서를 변경해야 합니다.

Suggested change
private void reviseBookReview(BookReview review, String description, double newRate) {
double oldRate = review.getRate();
review.updateBookReview(description, newRate);
if (oldRate != newRate) {
subtractSumRate(oldRate);
addSumRate(newRate);
}
}
private void reviseBookReview(BookReview review, String description, double newRate) {
double oldRate = review.getRate();
if (oldRate != newRate) {
subtractSumRate(oldRate);
review.updateBookReview(description, newRate);
addSumRate(newRate);
} else {
review.updateBookReview(description, newRate);
}
}

Comment on lines +104 to +108
assertSoftly(softly -> {
softly.assertThat(review.getDescription()).isEqualTo("수정");
softly.assertThat(review.getRate()).isEqualTo(2.0);
softly.assertThat(meeting.getSumRate()).isEqualTo(6.0);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

앞서 언급한 Meeting.reviseBookReview 메서드의 버그 수정에 맞춰, 테스트 코드의 기대값도 올바른 수학적 합계인 8.0으로 수정해야 합니다.

  • 기존 별점 합계: -9.0
  • 리뷰 1 추가 (4.0) -> -5.0
  • 리뷰 2 추가 (6.0) -> 1.0
  • 리뷰 1 수정 (4.0 -> 2.0):
    • subtractSumRate(4.0) 호출 시 sumRate(1.0) < oldRate(4.0)이므로 재계산이 트리거됩니다.
    • 수정 전 리뷰들의 실제 합계는 4.0 + 6.0 = 10.0이므로 sumRate10.0이 됩니다.
    • 이후 oldRate(4.0)을 차감하여 6.0이 되고, newRate(2.0)을 더해 최종적으로 **8.0**이 되어야 합니다.
Suggested change
assertSoftly(softly -> {
softly.assertThat(review.getDescription()).isEqualTo("수정");
softly.assertThat(review.getRate()).isEqualTo(2.0);
softly.assertThat(meeting.getSumRate()).isEqualTo(6.0);
});
assertSoftly(softly -> {
softly.assertThat(review.getDescription()).isEqualTo("수정");
softly.assertThat(review.getRate()).isEqualTo(2.0);
softly.assertThat(meeting.getSumRate()).isEqualTo(8.0);
});

Comment on lines +116 to +121
softly.assertThat(review.getDescription()).isEqualTo("수정");
softly.assertThat(review.getRate()).isEqualTo(2.0);
softly.assertThat(meeting.getSumRate()).isEqualTo(6.0);
softly.assertThat(review.getMeeting()).isSameAs(meeting);
});
verifyUpdateOrder(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

앞서 언급한 Meeting.reviseBookReview 메서드의 버그 수정에 맞춰, 서비스 레이어 테스트 코드의 기대값도 올바른 수학적 합계인 8.0으로 수정해야 합니다.

        assertSoftly(softly -> {
            softly.assertThat(review.getDescription()).isEqualTo("수정");
            softly.assertThat(review.getRate()).isEqualTo(2.0);
            softly.assertThat(meeting.getSumRate()).isEqualTo(8.0);
            softly.assertThat(review.getMeeting()).isSameAs(meeting);
        });

한줄평의 기존 별점을 차감한 뒤 리뷰를 변경하고 새 별점을 더하도록 순서를 보정한다.
서비스와 도메인의 책임 및 계산 순서를 컨벤션 문서에도 동일하게 반영한다.

Constraint: PR #293 리뷰 지적을 반영하되 API·DTO·JPA·트랜잭션 계약은 유지
Rejected: 리뷰 변경 후 fallback 합계 재계산 | 새 별점이 포함된 합계에 증감을 다시 적용해 6.0으로 왜곡됨
Confidence: high
Scope-risk: narrow
Directive: sumRate fallback은 대상 리뷰의 기존 별점이 컬렉션에 남아 있을 때 수행할 것
Tested: ./gradlew cleanTest test --tests checkmo.clubMeeting.internal.entity.MeetingTest --tests checkmo.clubMeeting.internal.service.command.ClubBookReviewCommandServiceTest
Not-tested: MySQL에서 손상된 sumRate를 재현하는 영속성 기반 시나리오
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: ClubMeeting 객체지향 도메인 책임 리팩토링

1 participant